package ca.concordia.soen6011.impl;

import java.util.Date;

import ca.concordia.soen6011.Result;
import ca.concordia.soen6011.interfaces.MathFunction;
import ca.concordia.soen6011.exceptions.InfinityOutputException;
import ca.concordia.soen6011.commons.Helpers;

/**
 * Tan (x) transient function
 * -----------------------------------------------------
 * 
 * Calculates the Tan(x)
 * 
 * @author Singh, Kawal Jeet
 * @since July-August 2014
 * 
 *        *****************************************************************
 *        TRANSIENT FUNCTION CALCULATOR is a team project for the SOEN 6011
 *        course delivered by Dr. Kamthan, Pankaj at Concordia University,
 *        Montreal for the summer 2014 session.
 *        *****************************************************************
 */
public class Tan implements MathFunction {

	@Override
	/*
	 * Computes value of Tan(x)
	 */
	public Result compute(double x, double precision)
			throws InfinityOutputException {

		x = x % 360;
		double tanValue = 0;
		Result result = new Result();
		Date start = new Date();

		if (x == 90) {
			throw new InfinityOutputException();
		} else if (x >= 0) {
			tanValue = calculate(x, precision);
		} else if (x < 0) {
			x = x * -1;
			tanValue = -1 * calculate(x, precision);
		}

		// Perform some logic, and then create and return the Result object and
		// return it.

		Date end = new Date();
		result.setComputationTime(end.getTime() - start.getTime());
		result.setOutput(tanValue);
		result.setPrecison(precision);
		return result;
	}

	// Calls the other functions in according to the input value of the x in
	// degrees
	public double calculate(double x, double precision) {
		double tanValue = 0;
		if (x > 0 && x < 90) {
			tanValue = getTan(x, precision);
		} else if (x > 90 && x < 180) {
			double degrees = 180 - x;
			tanValue = getTan(degrees, precision) * -1;

		} else if (x > 180) {
			x = x - 180;
			tanValue = getTan(x, precision);
		}
		return tanValue;
	}

	// Returns the value of tangent

	public double getTan(double x, double precision) {
		double tanValue = 0;
		if (x >= 0 && x <= 45) {
			tanValue = tanCompute(x, precision);

		} else if (x > 45 && x < 90) {
			double value = tanCompute(x - 45, precision);
			tanValue = (1 + value) / (1 - value);
		}

		return tanValue;
	}

	// Computes the value of tan using Series
	public double tanCompute(double x, double precisionValue) {

		double tanValue = 0.0, calculatedValue = 10.0;

		x = Helpers.degree2radians(x);
		double B, temp, temp1;
		int Bn, k, r;
		int ctr = 1;

		while (calculatedValue > precisionValue && ctr >= 1) {
			// Generate Bernoulli number which is used to calculate tanx
			B = 0;
			Bn = 2 * ctr;
			for (k = 0; k <= Bn; k++) {
				temp = 0;
				for (r = 0; r <= k; r++) {
					temp = temp + Helpers.power(-1, r) * Helpers.factorial(k)
							* Helpers.power(r, Bn)
							/ (Helpers.factorial(r) * Helpers.factorial(k - r));
				}
				B = B + temp / ((double) (k + 1));
			}

			calculatedValue = Helpers.power(-4, ctr)
					* (1 - Helpers.power(4, ctr)) * B
					* Helpers.power(x, 2 * ctr - 1)
					/ Helpers.factorial(2 * ctr);
			tanValue = tanValue + calculatedValue;
			ctr += 1;
		}
		return tanValue;
	}

	@Override
	public String getName() {
		return "Tan(x)";
	}

	@Override
	public void validate(double x, double precision) throws Exception {
		// TODO Auto-generated method stub

	}

}
